package authentication

import (
	"context"
	"encoding/json"
	"slices"
	"strconv"
	"strings"

	"gitee.com/liangziyisheng/caas-platform-mc/internal/consts"
	"gitee.com/liangziyisheng/caas-platform-mc/internal/dao"
	"gitee.com/liangziyisheng/caas-platform-mc/internal/model/entity"
	"gitee.com/liangziyisheng/caas-platform-mc/utility/authorization"
	"gitee.com/liangziyisheng/caas-platform-mc/utility/cache"
	"gitee.com/liangziyisheng/caas-platform-mc/utility/utils"

	"github.com/goflyfox/gtoken/gtoken"
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
	"github.com/gogf/gf/v2/crypto/gmd5"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/text/gstr"
)

type appShortInfo struct {
	Code string `json:"code"`
	Name string `json:"name"`
	Desc string `json:"desc"`
}

type loginData struct {
	Username string              `json:"username"`
	Fullname string              `json:"fullname"`
	Type     string              `json:"type"`
	Token    string              `json:"token"`
	Menus    map[string][]string `json:"menus"`
	AppMenus []appShortInfo      `json:"appMenus"`
}

var myAppInfo = appShortInfo{}
var localToken *gtoken.GfToken // 本地token,自身使用需要考虑登录登出
var extToken *gtoken.GfToken   // 外部token,其他应用使用主要考虑认证
func init() {
	// 初始化token
	localToken = &gtoken.GfToken{
		ServerName:      consts.ServerName,
		LoginPath:       "/login",
		LoginBeforeFunc: loginBeforeFunc,
		LoginAfterFunc:  loginAfterFunc,
		LogoutPath:      "/logout",
		CacheMode:       consts.CacheModeRedis,
		MultiLogin:      consts.MultiLogin, // 允许多点登陆
		CacheKey:        consts.TokenCacheKey,
		Timeout:         consts.SessionTimeout,
		MaxRefresh:      consts.MaxRefresh,
		EncryptKey:      []byte(consts.TokenEncryptKey),
		AuthAfterFunc:   authAfterFunc,
		MiddlewareType:  consts.GtokenMiddlewareType,
		TokenDelimiter:  "_",
	}
	// 初始化token
	extToken = &gtoken.GfToken{
		ServerName: consts.ServerName,
		LoginPath:  "/login",
		// LoginBeforeFunc: loginBeforeFunc,
		// LoginAfterFunc:  loginAfterFunc,
		LogoutPath:     "/logout",
		CacheMode:      consts.CacheModeRedis,
		MultiLogin:     consts.MultiLogin, // 允许多点登陆
		CacheKey:       consts.TokenCacheKey,
		Timeout:        consts.SessionTimeout,
		MaxRefresh:     consts.MaxRefresh,
		EncryptKey:     []byte(consts.TokenEncryptKey),
		AuthAfterFunc:  authAfterFunc,
		MiddlewareType: consts.GtokenMiddlewareType,
		TokenDelimiter: "_",
	}
	myAppInfo.Code = g.Cfg().MustGet(context.TODO(), "application.code").String()
}

func NewToken() *gtoken.GfToken {
	return localToken
}
func NewExtToken() *gtoken.GfToken {
	return extToken
}

func loginBeforeFunc(r *ghttp.Request) (string, interface{}) {
	// 登录逻辑
	var err error
	tenantCode := r.Get("tenantCode").String()
	username := r.Get("username").String()
	password := r.Get("password").String()
	// authType := r.Get("authType").Int()
	appName := r.Get("appName").String()
	if username == "" || password == "" || tenantCode == "" {
		r.Response.WriteJson(gtoken.Fail("租户ID和用户名和密码都必传"))
		r.ExitAll()
	}
	// if authType == 0 {
	// 	r.Response.WriteJson(gtoken.Fail("认证类型错误"))
	// 	r.ExitAll()
	// }
	ctx := r.GetCtx()
	user := entity.User{}
	cls := dao.User.Columns()
	dbpass, err := gmd5.Encrypt(password)
	if err != nil {
		r.Response.WriteJson(gtoken.Fail("服务器异常"))
		r.ExitAll()
	}

	// 首先确保租户正常,没有被禁止等
	trows := 0
	trows, err = dao.Tenant.Ctx(ctx).Where(dao.Tenant.Columns().Code, tenantCode).Where(dao.Tenant.Columns().Status, consts.TenantStatus[1].Value).Count()
	if err != nil {
		r.Response.WriteJson(gtoken.Fail("服务器异常"))
		r.ExitAll()
	}
	if trows == 0 {
		r.Response.WriteJson(gtoken.Fail("该租户被冻结,无法登陆"))
		r.ExitAll()
	}

	// 必需是可登陆用户且状态正常的用户
	urows := 0
	md := dao.User.Ctx(ctx).Where(cls.Status, consts.UserStatus[1].Value).Where(cls.Type, consts.UserTypes[0].Value)
	err = md.Where(cls.TenantCode, tenantCode).Where(cls.Username, username).Where(cls.Password, dbpass).ScanAndCount(&user, &urows, true)
	if err != nil {
		r.Response.WriteJson(gtoken.Fail("用户名或密码错误"))
		r.ExitAll()
	}
	if urows != 1 {
		r.Response.WriteJson(gtoken.Fail("用户名或密码错误"))
		r.ExitAll()
	}
	tokenKey := consts.TokenKey{}
	tokenKey.UserId = strconv.FormatInt(user.Id, 10)
	tokenKey.CompanyCode, _ = utils.GetCompanyCode(r.GetCtx(), user.EmployeeNo)
	tokenKey.TenantCode = user.TenantCode
	tokenKey.Username = user.Username
	tokenKey.IsPlatformAdmin = user.PlatformAdmin
	tokenKey.IsTenantAdmin = user.TenantAdmin
	// tokenKey.AuthType = authType
	tokenKey.AppName = appName
	tokenKeyByte, err := json.Marshal(tokenKey)
	if err != nil {
		r.Response.WriteJson(gtoken.Fail("token key生成失败"))
		r.ExitAll()
	}
	return consts.TokenBy + string(tokenKeyByte), user
}

func loginAfterFunc(r *ghttp.Request, respData gtoken.Resp) {
	ctx := r.GetCtx()
	if !respData.Success() {
		r.Response.WriteJson(respData)
		return
	} else {
		// respData.Code = 1
		userKey := respData.GetString("userKey")
		tokeyKeyStr := gstr.StrEx(userKey, consts.TokenBy)
		tokenKey := consts.TokenKey{}
		err := json.Unmarshal([]byte(tokeyKeyStr), &tokenKey)
		if err != nil {
			r.Response.WriteJson(gtoken.Fail("token key解析失败"))
			r.ExitAll()
		}
		userId, err := strconv.ParseInt(tokenKey.UserId, 10, 64)
		if err != nil {
			r.Response.WriteJson(gtoken.Fail("tokenkey里userid解析失败"))
			r.ExitAll()
		}
		user := entity.User{}
		err = dao.User.Ctx(context.TODO()).WherePri(userId).Scan(&user)
		if err != nil {
			r.Response.WriteJson(gtoken.Fail("token无效"))
			r.ExitAll()
		}
		// --------- 认证成功后先获取用户关联的应用及菜单
		menusMap := make(map[string][]string, 0)
		apps := make([]appShortInfo, 0)
		MCAppInfo := appShortInfo{
			Code: g.Cfg().MustGet(ctx, "application.code").String(),
			Name: g.Cfg().MustGet(ctx, "application.name").String(),
			Desc: g.Cfg().MustGet(ctx, "application.desc").String(),
		}
		// 如果是管理员，只能访问管理中心
		if user.TenantAdmin {
			menusMap = consts.TenantAdminMenus
			apps = append(apps, MCAppInfo)
		} else if user.PlatformAdmin {
			menusMap = consts.PlatformAdminMenus
			apps = append(apps, MCAppInfo)
		} else {
			// 先确定登陆用户是否具备许可
			lcls := dao.License.Columns()
			var lics []entity.License
			var total int
			err = dao.License.Ctx(ctx).Where(lcls.TenantCode, user.TenantCode).Where(lcls.Status, consts.LicenseStatus[1].Value).ScanAndCount(&lics, &total, true)
			if err != nil {
				r.Response.WriteJson(gtoken.Fail("该租户尚未获得任何许可,无法登陆"))
				r.ExitAll()
			}
			if total == 0 {
				r.Response.WriteJson(gtoken.Fail("该租户尚未获得任何许可,无法登陆"))
				r.ExitAll()
			}
			// 将授权的许可的应用列表扔进cache中
			cc, _ := cache.NewCache()
			cacheVal := "licensedTenantAppCache"
			licsApps := make(map[string]int64, 0)
			for _, val := range lics {
				licsApps[val.AppCode] = val.Id
				cacheVal = cacheVal + consts.ElemSeperator + val.AppCode
			}
			cc.Set(ctx, consts.CacheKeysPrefix.License+user.TenantCode, cacheVal, 0)

			// ============= 获取用户授权的菜单 =============
			e := authorization.NewEnforcer()
			roles, err := e.GetRolesForUser(tokenKey.UserId)
			if err != nil {
				r.Response.WriteJson(gtoken.Fail("无法获取用户角色"))
				r.ExitAll()
			}
			// 根据角色获取权限及前端路径
			if len(roles) > 0 {
				// rolesId := make([]int64, 0)
				// for _, r := range roles {
				// 	rolesId = append(rolesId, gconv.Int64(r))
				// }
				// var menusId []int64
				// 根据角色获取关联菜单
				rmcls := dao.RoleMenu.Columns()
				menusId, err := dao.RoleMenu.Ctx(ctx).Fields(rmcls.MenuId).WhereIn(rmcls.RoleName, roles).Array()
				if err != nil {
					r.Response.WriteJson(gtoken.Fail("无法获取到用户应用菜单"))
					r.ExitAll()
				}
				menusList := make([]entity.AppMenu, 0)
				err = dao.AppMenu.Ctx(ctx).WhereIn(dao.AppMenu.Columns().Id, menusId).Scan(&menusList)
				if err != nil {
					r.Response.WriteJson(gtoken.Fail("无法获取到用户应用菜单"))
					r.ExitAll()
				}

				// 进一步获取App名称
				// 解析所有app缓存
				cc, _ = cache.NewCache()
				cache.InitCache(ctx, consts.InitialCacheKeys.AppsKey, false)
				val, err := cc.Get(ctx, consts.InitialCacheKeys.AppsKey)
				if err != nil {
					r.Response.WriteJson(gtoken.Fail("读取应用缓存失败"))
					r.ExitAll()
				}
				cKey, ok := val.Val().(string)
				if !ok {
					r.Response.WriteJson(gtoken.Fail("读取应用缓存失败"))
					r.ExitAll()
				}
				appMap := make(map[string]entity.App, 0)
				json.Unmarshal([]byte(cKey), &appMap)

				for _, m := range menusList {
					// 如果参数里指定了登陆某个应用，则只返回该应用菜单，适合移动应用
					if tokenKey.AppName != "" && appMap[m.AppCode].Name != tokenKey.AppName {
						continue
					}
					// 当前菜单在许可应用范围内则加入菜单列表
					if _, ok := licsApps[m.AppCode]; ok {
						if _, ok := menusMap[m.AppCode]; !ok {
							menusMap[m.AppCode] = []string{m.Path}
						} else {
							menusMap[m.AppCode] = append(menusMap[m.AppCode], m.Path)
						}
					}
				}
				// 最后决出用户真正能用的应用列表
				for k := range menusMap {
					apps = append(apps, appShortInfo{
						Code: appMap[k].Code,
						Desc: appMap[k].Description,
						Name: appMap[k].Name,
					})
					// apps[appMap[k].Name] = appMap[k].Description
				}
			}
		}
		data := &loginData{
			Username: user.Username,
			Fullname: user.Fullname,
			Type:     consts.TokenType,
			Token:    respData.GetString("token"),
			Menus:    menusMap,
			AppMenus: apps,
		}
		// r.Response.WriteJsonExit(data)
		r.Response.WriteJson(gtoken.Succ(data))
	}
}

func authAfterFunc(r *ghttp.Request, respData gtoken.Resp) {
	if !respData.Success() {
		respData.Code = 401
		// respData.Msg = "token无效"
		r.Response.WriteJson(respData)
		return
	} else {
		userKey := respData.GetString("userKey")
		tokeyKeyStr := gstr.StrEx(userKey, consts.TokenBy)
		tokenKey := consts.TokenKey{}
		err := json.Unmarshal([]byte(tokeyKeyStr), &tokenKey)
		if err != nil {
			r.Response.WriteJson(gtoken.Resp{Code: 401, Msg: "token key解析失败", Data: ""})
			r.ExitAll()
		}
		// 检查当前租户所操作APP是否被许可
		if !tokenKey.IsTenantAdmin && !tokenKey.IsPlatformAdmin {
			cc, _ := cache.NewCache()
			licAppsVar, err := cc.Get(r.Context(), consts.CacheKeysPrefix.License+tokenKey.TenantCode)
			if err != nil {
				r.Response.WriteJson(gtoken.Resp{Code: 403, Msg: "该应用尚未开通许可,请开通许可后再使用!", Data: ""})
				r.ExitAll()
			}
			licAppsArr := strings.Split(licAppsVar.String(), consts.ElemSeperator)
			// 读取本实例应用信息
			if !slices.Contains(licAppsArr, myAppInfo.Code) {
				r.Response.WriteJson(gtoken.Resp{Code: 403, Msg: "该应用尚未开通许可,请开通许可后再使用!", Data: ""})
				r.ExitAll()
			}
		}
		r.SetCtxVar(consts.TokenClaims.Auth, r.Header.Get("Authorization"))
		r.SetCtxVar(consts.TokenClaims.UserId, tokenKey.UserId)
		r.SetCtxVar(consts.TokenClaims.Username, tokenKey.Username)
		r.SetCtxVar(consts.TokenClaims.TenantCode, tokenKey.TenantCode)
		r.SetCtxVar(consts.TokenClaims.CompanyCode, tokenKey.CompanyCode)
		r.SetCtxVar(consts.TokenClaims.PlatformAdmin, tokenKey.IsPlatformAdmin)
		r.SetCtxVar(consts.TokenClaims.TenantAdmin, tokenKey.IsTenantAdmin)
		r.Middleware.Next()
	}
}

// ValidToken 验证token对当前应用是否有效
func ValidToken(r *ghttp.Request) {
	respData := extToken.GetTokenData(r)
	if !respData.Success() {
		r.Response.WriteJson(gtoken.Fail("token无效"))
		// r.Response.WriteStatusExit(401, "token无效")
		r.ExitAll()
	} else {
		extToken.AuthAfterFunc(r, respData)
	}

	r.Middleware.Next()
}
